import { Page } from "puppeteer-core";
import { Spider } from "./Spider";
import { Task } from "./TaskController";

/** 提取器错误 */
export class ExtractorEroor extends Error { };

export class Extractor {
    /**
     * @param page 
     * @param spider 
     * @param task 
     */
    constructor(protected page: Page, protected spider: Spider, protected task: Task) { }

    /** 
     * 当所有的handle执行完之后调用的函数
     * @description 在这里主要手动Handle、验证爬取结果、给其他spider添加任务，不建议在这里写数据保存逻辑。
     * @throws {ExtractorEroor} 如果抛出异常，将阻止后续代码执行
     */
    async handleAfterHook(): Promise<void> { }

    /**
     * 在所有的 handle 之前执行
     * @param page 当前页面
     * @param task 当前任务
     * @returns 返回 true 表示通过，返回 false 表示阻止(建议返回 PASS 和 STOP 静态属性) 
     * @throws {ExtractorEroor} 如果抛出异常，将阻止后续代码执行
     * @description 你可以在这里编写一些爬取的前置条件，不满足(比如 常见404或者500)则不进行下一步提取
     */
    async handleBeforeHook(): Promise<void> { }

    /**
     * 被爬虫加载的时候执行，用来初始化或者调整page,只会调用一次。
     * @param pages spider 的所有 page
     */
    static loadHook(pages: Page[]): void { }

    /**
     * 当这个函数被调用时，你可以安心的保存的你爬取的数据。因为数据是经过 handleAfterHook 校验的。
     * @param extractor 
     * @description 此时你不应该在调用 extractor 参数上的任何方法。只使用它的属性。
     */
    static async saveDataHook(extractor: Extractor): Promise<void> { }

    /** 
     * 在创建对象之前的处理器。
     */
    static handles: Array<(page: Page) => Promise<void>> = []

    /**
     * 创建一个自己，异步，等待创建完毕则所有提取器都已初始化完毕
     * @param page 当前使用的页面
     * @param spider 所属爬虫
     * @param task 当前进行的任务
     * @throws {Error} handleBeforeHook 阻止继续处理
     */
    static async create(page: Page, spider: Spider, task: Task) {

        const extractor = new this(page, spider, task);

        // 调用前置函数
        await extractor.handleBeforeHook()
        /**
         * 在创建自己之前，等待着所有的 handle 完成
         * 因为只有这样，才能保证再调用 success 方法之前，将所有数据加载完毕
         */
        for (const handle of this.handles) {
            await handle.call(extractor, page).catch((e: Error) => console.warn(e.message));
        }
        // 调用后置函数
        await extractor.handleAfterHook()
        // 调用存储函数
        await this.saveDataHook(extractor);

        return extractor;
    };
}